<?php

namespace soa\express\express;

use artisan\cache;
use artisan\db;

class ExpressBrandParser
{

    /**
     * default: five hours
     *
     * @var integer
     */
    const EXPRESS_BRAND_LIST_CACHE_TIME = 18000;
    const QUERY_RESULT_CACHE_TIME = 3600;
    const QUERY_RESULT_CACHE_KEY = 'queryResultKey';
    /**
     * DB configs
     *
     * @var string
     */
    const DB_NAME = 'express_infomation';
    const TABLE_NAME = 'data_source_detail';
    /**
     * ExpressBrandList
     *
     * @var array|null
     */
    protected static $ExpressBrandList;
    /**
     * express brand map
     *
     * @var array
     */
    protected static $ExpressBrandMap = [
        '圆通|YTO|^yt$'            => [
            'code' => 'yt',
            'name' => '圆通速递',
        ],
        '韵达|YUNDA|^yd$'          => [
            'code' => 'yd',
            'name' => '韵达快递',
        ],
        '中通|ZTO|^zt$'            => [
            'code' => 'zt',
            'name' => '中通快递',
        ],
        '申通|STO'                 => [
            'code' => 'sto',
            'name' => '申通快递',
        ],
        '顺丰|sf'                  => [
            'code' => 'sf',
            'name' => '顺丰速运',
        ],
        '邮政EMS|ems'              => [
            'code' => 'ems',
            'name' => 'EMS',
        ],
        '天天|TTKDEX|^tt$'         => [
            'code' => 'tt',
            'name' => '天天快递',
        ],
        '全峰|qf'                  => [
            'code' => 'qf',
            'name' => '全峰快递',
        ],
        '汇通|百世快递|百世快运|HTKY|^ht$' => [
            'code' => 'ht',
            'name' => '百世快递',
        ],
        '京东|jd'                  => [
            'code' => 'jd',
            'name' => '京东快递',
        ],
        '苏宁|sn'                  => [
            'code' => 'sn',
            'name' => '苏宁快递',
        ],
        '国通|CCES|GTO|gt'         => [
            'code' => 'gt',
            'name' => '国通快递',
        ],
        '邮政|post'                => [
            'code' => 'post',
            'name' => '邮政包裹',
        ],
        '聚美|jumei'               => [
            'code' => 'jumei',
            'name' => '聚美优品',
        ],
        '品骏|pj'                  => [
            'code' => 'pj',
            'name' => '品骏快递',
        ],
        '快捷|kj'                  => [
            'code' => 'kj',
            'name' => '快捷速递',
        ],
        'DHL'                    => [
            'code' => 'dhl',
            'name' => 'DHL快递',
        ],
        'UPS'                    => [
            'code' => 'ups',
            'name' => 'UPS快递',
        ],
        '汇强|hq'                  => [
            'code' => 'hq',
            'name' => '汇强快递',
        ],
        '优速|UC|ys'               => [
            'code' => 'ys',
            'name' => '优速物流',
        ],
        '龙邦|lb'                  => [
            'code' => 'lb',
            'name' => '龙邦速递',
        ],
        '联邦国际|fedexInter'        => [
            'code' => 'fedexInter',
            'name' => '联邦国际',
        ],
        '联邦国内|fedex'             => [
            'code' => 'fedex',
            'name' => '联邦国内',
        ],
        '联昊通|lht'                => [
            'code' => 'lht',
            'name' => '联昊通',
        ],
        '能达|nd'                  => [
            'code' => 'nd',
            'name' => '能达速递',
        ],
        '全一|qy'                  => [
            'code' => 'qy',
            'name' => '全一快递',
        ],
        '全日通|qrt'                => [
            'code' => 'qrt',
            'name' => '全日通',
        ],
        '如风达|rfd'                => [
            'code' => 'rfd',
            'name' => '如风达',
        ],
        '速尔|se'                  => [
            'code' => 'se',
            'name' => '速尔快递',
        ],
        '德邦|DBKY|dp|debang'      => [
            'code' => 'dp',
            'name' => '德邦物流',
        ],
        '宅急送|zjs'                => [
            'code' => 'zjs',
            'name' => '宅急送',
        ],
        '大洋|dy'                  => [
            'code' => 'dy',
            'name' => '大洋物流',
        ],
        '万象|wx'                  => [
            'code' => 'wx',
            'name' => '万象物流',
        ],
        '赛澳递|sad'                => [
            'code' => 'sad',
            'name' => '赛澳递速递',
        ],
        '远长|yc'                  => [
            'code' => 'yc',
            'name' => '远长快递',
        ],
        '宽容|宽荣|kr'               => [
            'code' => 'kr',
            'name' => '宽容物流',
        ],
        '增益|zy'                  => [
            'code' => 'zy',
            'name' => '增益快递',
        ],
        '城市100|city100'          => [
            'code' => 'city100',
            'name' => '城市100快递',
        ],
        '银捷快递|银捷速递|yj'           => [
            'code' => 'yj',
            'name' => '银捷快递',
        ],
        '百世物流|BESTQJT|htwl'      => [
            'code' => 'htwl',
            'name' => '百世物流',
        ],
        '易通达|ytd'                => [
            'code' => 'ytd',
            'name' => '海口易通达',
        ],
        '信丰|xf'                  => [
            'code' => 'xf',
            'name' => '信丰物流',
        ],
    ];
    /**
     * brandInfoCache
     *
     * @var array
     */
    private static $brandInfoCache = [];

    /**
     * query cache
     *
     * @var array
     */
    private static $queryCache = [];

    /**
     * getBrandInfo
     *
     * @param  string $brandStr
     * @return array
     */
    public static function getBrandInfoByBrandName($brandStr = null)
    {
        $data = [
            'code' => '',
            'name' => $brandStr,
        ];
        if (!is_string($brandStr) || !$brandStr) {
            return $data;
        }
        if (!isset(static::$queryCache[$brandStr])) {
            $cacheKey = static::buildCacheKey(static::QUERY_RESULT_CACHE_KEY . ':' . (strlen($brandStr) > 40 ? md5($brandStr) : $brandStr));
            if (!($cachedData = cache::get($cacheKey))) {
                if ($matched = static::match($brandStr, static::$ExpressBrandMap, 'localSource') ?: static::match($brandStr, static::getSupportedExpressBrandMap(false), 'DBSource')) {
                    $data = $matched;
                }
                static::$queryCache[$brandStr] = $data;
                cache::set($cacheKey, serialize($data), static::QUERY_RESULT_CACHE_TIME);
            } else {
                static::$queryCache[$brandStr] = is_array($cachedData) ? $cachedData : unserialize($cachedData);
            }
        }
        return static::$queryCache[$brandStr];
    }

    /**
     * match
     *
     * @param  string $brandStr
     * @param  array $brandList
     * @return bool|array
     */
    private static function match($brandStr, array $brandList, $cacheKey = null)
    {
        $data = false;
        if ($brandStr && $brandList) {
            $cacheKey || $cacheKey = static::buildCacheKey($brandList);
            if (count($brandList) > 90) {
                $brandList = array_chunk($brandList, 90);
            } else {
                $brandList = [$brandList];
            }
            foreach ($brandList as $key => $item) {
                if (!isset(static::$brandInfoCache[$cacheKey][$key])) {
                    static::$brandInfoCache[$cacheKey][$key] = static::buildPatternPair($item);
                }
                $brandInfoData = static::$brandInfoCache[$cacheKey][$key];
                if ($brandInfoData && preg_match($brandInfoData['pattern'], $brandStr, $match)) {
                    unset($match[0]);
                    $match = array_filter($match);
                    if (count($match) === 1) {
                        $data = $brandInfoData['data'][key($match) - 1];
                        unset($data['pattern']);
                        break;
                    }
                }
            }
        }
        return $data;
    }

    /**
     * buildPatternPair
     *
     * @param  array $formattedBrandList
     * @return array
     */
    private static function buildPatternPair(array $formattedBrandList)
    {
        if ($formattedBrandList) {
            $keys = array_keys($formattedBrandList);
            return [
                'pattern' => '#(' . (implode(')|(', $keys)) . ')#i',
                'data'    => array_values($formattedBrandList),
            ];
        }
        return [];
    }

    /**
     * foreach to get the brand
     *
     * @param  string $brandStr
     * @return array
     */
    public static function getBrand($brandStr)
    {
        foreach (static::$ExpressBrandMap as $key => $brand) {
            if (preg_match('/' . $key . '/i', $brandStr)) {
                return $brand;
            }
        }
        return [
            'code' => '',
            'name' => $brandStr,
        ];
    }

    /**
     * getSupportedExpressBrands
     *
     * @param  bool $mergeLocal
     * @return array
     */
    private static function getSupportedExpressBrandMap($mergeLocal = false)
    {
        if (static::$ExpressBrandList === null) {
            static::$ExpressBrandList = [];
            $key = static::buildCacheKey(__FUNCTION__);
            if (!($brandList = cache::get($key))) {
                $conditions = [
                    'status' => 'Y',
                ];
                if (($brandList = db::connect(static::DB_NAME)->table(static::TABLE_NAME)->getAll($conditions, null, 'ishot DESC')) && is_array($brandList)) {
                    $brandList = static::diffWithLocalDataByCode($brandList, 'express_brand');
                    static::$ExpressBrandList = static::formatExpressBrands($brandList);
                    cache::set($key, serialize(static::$ExpressBrandList), static::EXPRESS_BRAND_LIST_CACHE_TIME);
                }
            } else {
                static::$ExpressBrandList = is_array($brandList) ? $brandList : unserialize($brandList);
            }
        }
        //merge local and remove duplicate, local is first
        return $mergeLocal ? static::filterDuplicate(static::$ExpressBrandMap + static::$ExpressBrandList) : static::$ExpressBrandList;
    }

    /**
     * @param array $data
     * @return array
     */
    private static function filterDuplicate(array $data)
    {
        $filteredData = [];
        $codeSet = [];
        foreach ($data as $pattern => $val) {
            //remove or merge ? remove
            if (!in_array($val['code'], $codeSet)) {
                $codeSet[] = $val['code'];
                $filteredData[$pattern] = $val;
            }
        }
        return $filteredData;
    }

    /**
     * @param array $data
     * @param string $compareKey
     * @return array
     */
    private static function diffWithLocalDataByCode(array $data, $compareKey = '')
    {
        $filteredData = [];
        if ($data && $compareKey) {
            $codeSet = array_column(static::$ExpressBrandMap, 'code');
            $filteredData = array_diff_key(array_column($data, null, $compareKey), array_flip($codeSet));
        }
        return $filteredData;
    }

    /**
     * formatExpressBrands
     *
     * @param  array $brandList
     * @return array
     */
    private static function formatExpressBrands(array $brandList)
    {
        $formattedBrandList = [];
        if (count($brandList)) {
            foreach ($brandList as $brand) {
                if (!empty($brand['pattern'])) {
                    $formattedBrandList[$brand['pattern']] = [
                        'code' => $brand['code'],
                        'name' => $brand['name'],
                    ];
                } else {
                    $formattedBrandList[$brand['short_name'] . '|' . $brand['express_brand']] = [
                        'code' => $brand['express_brand'],
                        'name' => $brand['name'],
                    ];
                }

            }
        }
        return $formattedBrandList;
    }

    /**
     * keyBuilder
     *
     * @param  string|array|object $key
     * @return string
     */
    private static function buildCacheKey($key)
    {
        if (is_array($key) || is_object($key)) {
            return md5(json_encode($key));
        }
        return 'OrderImport:ExpressBrands:' . (string)$key;
    }
}
